/** @file lcd.c
 *
 * @author cameris camerisdump@gmail.com
 */

#include "lcd.h"
#include <avr/io.h>
#include <util/delay.h>

#define LCD_SET_CS()  LCD_CMD_PORT  |=  (1<<LCD_CS)
#define LCD_CLR_CS()  LCD_CMD_PORT  &= ~(1<<LCD_CS)

#define LCD_SET_RS()  LCD_CMD_PORT  |=  (1<<LCD_RS)
#define LCD_CLR_RS()  LCD_CMD_PORT  &= ~(1<<LCD_RS)

#define LCD_SET_WR()  LCD_CMD_PORT  |=  (1<<LCD_WR)
#define LCD_CLR_WR()  LCD_CMD_PORT  &= ~(1<<LCD_WR)

#define LCD_SET_RD()  LCD_CMD_PORT  |=  (1<<LCD_RD)
#define LCD_CLR_RD()  LCD_CMD_PORT  &= ~(1<<LCD_RD)

#define BLSTEPS 20 /**< @brief size of backlight pwm table */

/** @brief backlight pwm table */
const __flash uint8_t blpwm[BLSTEPS] = {
	1, 2, 3, 5, 7, 10, 13, 17, 21, 27, 35, 44, 55, 69, 86, 107,
	133, 165, 205, 255
};

uint_fast8_t blindex; /**< @brief current index in the backlight pwm table */

/** @brief set backlight to value
 * @param index index in blpwm
 * @return returns the set index */
uint_fast8_t lcd_set_bl(uint_fast8_t index)
{ /* {{{ */
	if (index >= BLSTEPS) index = BLSTEPS-1;
	blindex = index;
	LCD_BL_OCR = blpwm[blindex];
	return blindex;
} /* }}} */

/** @brief increase or decrease backlight
 * @param d steps to increase or decrease
 * @return returns the new index */
uint_fast8_t lcd_set_relative_bl(int_fast8_t d)
{ /* {{{ */
	if (d == 0) return blindex;
	if ( (-1*d) >= blindex) {
		blindex = 0;
	} else {
		blindex += d;
	}
	return lcd_set_bl(blindex);
} /* }}} */

/** @brief initializes the backlight pwm */
void lcd_init_bl(void)
{ /* {{{ */
	/* fast pwm */
	LCD_BL_TIMERA = (1<<LCD_COM1)|(1<<LCD_WGM1)|(1<<LCD_WGM0);

	/* prescaler 64 */
	LCD_BL_TIMERB = (1<<LCD_CS2);
} /* }}} */

/** @brief write command for the controller
 * @param cmd the command to wirte */
void lcd_write_cmd(uint8_t cmd)
{ /* {{{ */
	LCD_CLR_RS();
	LCD_HI_PORT = (cmd>>8);
	LCD_LO_PORT = (cmd);
	LCD_CLR_WR();
	LCD_SET_WR();

	LCD_SET_RS(); // always asume data
} /* }}} */

/** @brief writes data to the controller
 * @param dat the data to write */
void lcd_write_data(uint16_t dat)
{ /* {{{ */
	LCD_HI_PORT = (dat>>8);
	LCD_LO_PORT = (dat);
	LCD_CLR_WR();
	LCD_SET_WR();
} /* }}} */

/** @brief writes command and data to the controller
 *
 * writes command and data to the controller, only useful for single writes.
 * @param cmd the command to set
 * @param dat data to set */
void lcd_set_reg(uint8_t cmd, uint16_t dat) {
	lcd_write_cmd(cmd);
	lcd_write_data(dat);
}

/** @brief initializes the ports */
void lcd_init_ports(void)
{ /* {{{ */
	/* set default */
	LCD_SET_CS();
	LCD_SET_RS();
	LCD_SET_WR();
	LCD_SET_RD();

	LCD_CMD_DDR |= (1<<LCD_CS)|(1<<LCD_RS)|(1<<LCD_RD)|(1<<LCD_WR)|(1<<LCD_BL_PIN);
	LCD_LO_DDR |= 0xFF;
	LCD_HI_DDR |= 0xFF;

	LCD_CLR_CS(); // clear to enable lcd
} /* }}} */

/** @brief initializes the controller */
void lcd_init(void)
{ /* {{{ */
	lcd_init_ports();
	lcd_init_bl();

	/* start oscillation OSCEN=1 */
	lcd_set_reg(0x00, 0x0001);
	_delay_ms(15);

	/* power control (1) */
	lcd_set_reg(0x03, 0xA8A4);
	/* power control (2) */
	lcd_set_reg(0x0C, 0x0000);
	/* power control (3) */
	lcd_set_reg(0x0D, 0x080C);
	/* power control (4) */
	lcd_set_reg(0x0E, 0x2B00);
	/* power control (5) */
	lcd_set_reg(0x1E, 0x00B7);
	_delay_ms(15);

#if LCD_LAYOUT == LAYOUT_WIDE
	/* driver output control RL=0, REV=1, BGR=1, TB=0 */
	lcd_set_reg(0x01, 0x293F);
#else
	/* driver output control RL=1, REV=1, BGR=1, TB=1 */
	lcd_set_reg(0x01, 0x693F);
#endif
	/* frame control */
	/*lcd_set_reg(0x02, 0x0601);*/
	lcd_set_reg(0x02, 0x0000);

	/* restore VSYNC mode from low power state */
	lcd_set_reg(0x10, 0x0000);

#if LCD_LAYOUT == LAYOUT_WIDE
	/* entry mode AM = 0 */
	lcd_set_reg(0x11, 0x6030);
#else
	/* entry mode AM = 1 */
	lcd_set_reg(0x11, 0x6038);
#endif
	_delay_ms(20);

	/* compare register */
	lcd_set_reg(0x05, 0x0000);
	lcd_set_reg(0x06, 0x0000);
	/* horizontal and vertical porch are for DOTCLK mode operation */
	lcd_set_reg(0x16, 0xEF1C); /* horizontal porch */
	lcd_set_reg(0x17, 0x0003); /* vertical porch */

	/* display control */
	lcd_set_reg(0x07, 0x0233); /* display control */
	/*lcd_set_reg(0x0B, 0x5311); [> frame cycle control <]*/
	lcd_set_reg(0x0B, 0x5308); /* frame cycle control */
	lcd_set_reg(0x0F, 0x0000); /* gate scan position */
	_delay_ms(20);

	/* vertical scroll controll */
	lcd_set_reg(0x41, 0x0000);
	lcd_set_reg(0x42, 0x0000);

	/* 1st screen driving position */
	lcd_set_reg(0x48, 0x0000); /* start (0) */
	lcd_set_reg(0x49, 0x013F); /* end (319) */

	/* source RAM address */
	lcd_set_reg(0x44, 0xEF00); /* horizontal RAM address start/end */
	lcd_set_reg(0x45, 0x0000); /* vertical RAM address start */
	lcd_set_reg(0x46, 0x013F); /* vertical RAM address end */
	_delay_ms(20);

	/* gamma control */
	lcd_set_reg(0x30, 0x0707);
	lcd_set_reg(0x31, 0x0204);
	lcd_set_reg(0x32, 0x0204);
	lcd_set_reg(0x33, 0x0502);
	lcd_set_reg(0x34, 0x0507);
	lcd_set_reg(0x35, 0x0204);
	lcd_set_reg(0x36, 0x0204);
	lcd_set_reg(0x37, 0x0502);
	lcd_set_reg(0x3A, 0x0302);
	lcd_set_reg(0x3B, 0x0302);

	_delay_ms(20);

	/* clear display with black */
	lcd_set_window(0,0,LCD_SIZE_X-1,LCD_SIZE_Y-1);
	for (uint_fast16_t y=0;y<LCD_SIZE_Y;y++){
		for (uint_fast16_t x=0;x<LCD_SIZE_X;x++){
			lcd_write_data(0x0000);
		}
	}
} /* }}} */

/** @brief sets the writing address for following data
 * @param x the x coordinate
 * @param y the y coordinate */
void lcd_set_address(uint16_t x, uint16_t y)
{ /* {{{ */
#if LCD_LAYOUT == LAYOUT_WIDE
	lcd_set_reg(0x4E, y);
	lcd_set_reg(0x4F, x);
#else
	lcd_set_reg(0x4E, x);
	lcd_set_reg(0x4F, y);
#endif
	lcd_write_cmd(0x22);
} /* }}} */

/** @brief sets a window in which to write data
 * @param x1 x coordinate of the upper left corner
 * @param y1 y coordinate of the upper left corner
 * @param x2 x coordinate of the lower right corner
 * @param y2 y coordinate of the lower right corner */
void lcd_set_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{ /* {{{ */
#if LCD_LAYOUT == LAYOUT_WIDE
	lcd_set_reg(0x44,((y2<<8)|y1) );
	lcd_set_reg(0x45, x1);
	lcd_set_reg(0x46, x2);
#else
	lcd_set_reg(0x44,((x2<<8)|x1));
	lcd_set_reg(0x45, y1);
	lcd_set_reg(0x46, y2);
#endif
	lcd_set_address(x1, y1);
} /* }}} */

/* vim:set foldmethod=marker: */
